#pragma once
#include "../acl_cpp_define.hpp"
#include <list>
#include "../stdlib/locker.hpp"

namespace acl
{

class connect_manager;
class connect_client;

/**
 * 客户端连接池类，实现对连接池的动态管理，该类为纯虚类，需要子类实现
 * 纯虚函数 create_connect 用于创建与服务端的一个连接，当允许该类
 * 对象允许通过 set_delay_destroy() 设置延迟销毁时，该类的子类实例
 * 必须是动态对象
 */
class ACL_CPP_API connect_pool
{
public:
	/**
	 * 构造函数，保存送入的参数到本类中
	 * @param addr {const char*} 服务器监听地址，格式：ip:port(domain:port)
	 * @param max {size_t} 连接池最大连接个数限制，如果该值设为 0，则不设置
	 *  连接池的连接上限
	 * @param idx {size_t} 该连接池对象在集合中的下标位置(从 0 开始)
	 */
	connect_pool(const char* addr, size_t max, size_t idx = 0);

	/**
	 * 销毁连接池集合中的所有connect_client*
	 */
	virtual ~connect_pool();

	/////////////////////////////////////////////////////////////////////

	/**
	 * 设置连接池的存活状态
	 * @param ok {bool} 设置该连接是否正常
	 */
	void set_alive(bool ok /* true | false */);

	/**
	 * 检查连接池是否正常，当连接池有问题时，该函数还会检测该连接池是否应该自动恢复，如果
	 * 允许恢复，则将该连接池又置为可用状态 if (retry_inter_ > 0 && now - last_dead_ >= retry_inter_)
	 * @return {bool} 返回 true 表示当前连接池处于正常状态，否则表示当前连接池不可用
	 */
	bool aliving();

	/**
	 * 设置连接池异常的重试时间间隔
	 * @param retry_inter {int} 当连接断开后，重新再次打开连接的时间间隔(秒)，
	 *  当该值 <= 0 时表示允许连接断开后可以立即重连，否则必须超过该时间间隔后才
	 *  允许断开重连；未调用本函数时，内部缺省值为 1 秒
	 * @return {connect_pool&}
	 */
	connect_pool& set_retry_inter(int retry_inter);

	/////////////////////////////////////////////////////////////////////

	/**
	 * 此接口用来设置超时时间
	 * @param conn_timeout {int} 网络连接超时时间(秒)
	 * @param rw_timeout {int} 网络 IO 超时时间(秒)
	 */
	connect_pool& set_timeout(int conn_timeout, int rw_timeout);

	/////////////////////////////////////////////////////////////////////

	/**
	 * 设置连接池中空闲连接的空闲生存周期
	 * @param ttl {time_t} 空闲连接的生存周期，当该值 < 0 则表示空闲连接不过期，
	 *  == 0 时表示立刻过期，> 0 表示空闲该时间段后将被释放
	 * @return {connect_pool&}
	 */
	connect_pool& set_idle_ttl(time_t ttl);

	/**
	 * 设置自动检查空闲连接的时间间隔，缺省值为 30 秒
	 * @param n {int} 时间间隔
	 * @return {connect_pool&}
	 */
	connect_pool& set_check_inter(int n);

	/**
	 * 从右往左倒序遍历连接池中的句柄，检查连接池中空闲的连接，将过期的连接释放掉
	 * @param ttl {time_t} 空闲时间间隔超过此值的连接将被释放
	 * @param exclusive {bool} 内部是否需要加锁
	 * @return {int} 被释放的空闲连接个数
	 */
	int check_idle(time_t ttl, bool exclusive = true);

	/////////////////////////////////////////////////////////////////////

	/**
	 * 1.检查连接是否可用alive_(不可用检查retry_inter_ > 0 && now - last_dead_ >= retry_inter_,是否可恢复)
	 * 2.从连接池list中取出一个connect_client*，如果list不为空，取走第一个connect_client*(已经是建立过连接的句柄)并在list中删除
	 * 3.如果list为空,查看max_ > 0 && count_ >= max_,决定是否连接池超限
	 * 4.创建一个新的connect_client* conn，并调用conn->open()打开连接返回给用户使用(	count_++; total_used_++; current_used_++;)
	 *
	 * 从连接池中尝试性获取一个连接，当服务器不可用、距上次服务端连接异常时间间隔
	 * 未过期或连接池连接个数达到连接上限则将返回 NULL；当创建一个新的与服务器的
	 * 连接时失败，则该连接池会被置为不可用状态
	 * @return {connect_client*} 如果为空则表示该服务器连接池对象不可用
	 */
	connect_client* peek();

	/**
	 * 1.如果设置了delay_destroy_,放回连接池中的句柄都被销毁count_--,如果count_ <= 0销毁连接池
	 * 2.检查keep && alive_,如果为false放回连接池中的句柄都被销毁count_--,如果为true将归还的connect_client*加入到连接池list中，并给该connect_client*加上归还时间when
	 * 3.调用check_idle检查连接池中空闲的连接(idle_ttl_ >= 0 && now - last_check_ >= check_inter_)，将过期的连接释放掉（即now - when 与 ttl比较）
	 *
	 * 释放一个连接至连接池中，当该连接池对应的服务器不可用或调用者希望关闭该连接时，
	 * 则该连接将会被直接释放
	 * @param conn {redis_client*}
	 * @param keep {bool} 是否针对该连接保持长连接
	 */
	void put(connect_client* conn, bool keep = true);

	/**
	 * add by qbyao
	 * 将句柄归还给连接池的过程由子类实现一些清理工作
	 */
	virtual void put_call_back(connect_client* conn) = 0;

	/////////////////////////////////////////////////////////////////////

	/**
	 * @return {const char*} 返回连接池的参数信息
	 */
	const char* get_pool_info();

	/**
	 * 获取连接池的服务器地址
	 * @return {const char*} 返回非空地址
	 */
	const char* get_addr() const
	{
		return addr_;
	}

	/**
	 * 获取连接池最大连接数限制，如果返回值为 0 则表示没有最大连接数限制
	 * @return {size_t}
	 */
	size_t get_max() const
	{
		return max_;
	}

	/**
	 * 获取连接池当前连接数个数
	 * @return {size_t}
	 */
	size_t get_count() const
	{
		return count_;
	}

	/**
	 * 获得该连接池对象在连接池集合中的下标位置
	 * @return {size_t}
	 */
	size_t get_idx() const
	{
		return idx_;
	}

	/**
	 * 重置统计计数器
	 * @param inter {int} 统计的时间间隔
	 */
	void reset_statistics(int inter)
	{
		time_t now = time(NULL);
		lock_.lock();
		if (now - last_ >= inter)
		{
			last_ = now;
			current_used_ = 0;
		}
		lock_.unlock();
	}

	/**
	 * 获取该连接池总共被使用的次数
	 */
	unsigned long long get_total_used() const
	{
		return total_used_;
	}

	/**
	 * 获取该连接池当前的使用次数
	 * @return {unsigned long long}
	 */
	unsigned long long get_current_used() const
	{
		return current_used_;
	}

protected:
	/**
	 * 纯虚函数，需要子类实现，创建新的数据库连接句柄
	 * @return {connect_client*}
	 */
	virtual connect_client* create_connect() = 0;

	friend class connect_manager;

	/**
	 * 设置该连接池对象为延迟自销毁，当内部发现引用计数为 0 时会自行销毁
	 */
	void set_delay_destroy();

protected:
	locker lock_;				         // 访问 pool_ 时的互斥锁

	char  addr_[256];			        // 连接池对应的服务器地址，IP:PORT
	size_t idx_;				        // 该连接池对象在集合中的下标位置
	size_t max_;				        // 最大连接数

	size_t count_;				        // 当前的连接数(连接池中已经建立连接的句柄,包括正在使用和空闲的句柄)
	unsigned long long total_used_;		// 该连接池的所有访问量

	// 统计某段实际使用量
	unsigned long long current_used_;	// 某时间段内的访问量
	time_t last_;				        // 上次记录的时间截

	std::list<connect_client*> pool_;	// 连接池集合

	// 函数aliving()和peek()中使用
	bool  alive_;				         // 是否属正常
	time_t last_dead_;			         // 该连接池对象上次不可用时的时间截
	int   retry_inter_;                  // 有问题的服务器的可以重试的时间间隔，不可用连接池对象再次被启用的时间间隔

	// peek()中使用,给新建连接句柄设置
	int   conn_timeout_;			     // 网络连接超时时间(秒)
	int   rw_timeout_;			         // 网络 IO 超时时间(秒)

	// 函数put()中使用
	bool  delay_destroy_;			     // 是否设置了延迟自销毁(放回连接池中的句柄都被销毁,如果count_ <= 0销毁连接池)

	// 函数put()中使用
	time_t idle_ttl_;			         // 空闲连接的生命周期
	time_t last_check_;			         // 上次检查空闲连接的时间截
	int   check_inter_;			         // 检查空闲连接的时间间隔
};

class ACL_CPP_API connect_guard
{
public:
	connect_guard(connect_pool& pool)
		: keep_(true), pool_(pool), conn_(NULL)
	{
	}

	virtual ~connect_guard(void)
	{
		if (conn_)
			pool_.put(conn_, keep_);
	}

	void set_keep(bool keep)
	{
		keep_ = keep;
	}

	connect_client* peek(void)
	{
		conn_ = pool_.peek();
		return conn_;
	}

protected:
	bool keep_;
	connect_pool& pool_;
	connect_client* conn_;
};

} // namespace acl
